feat(tbor): implement RSA-AES key unwrap (GetUnwrappingKey + UnwrapKey) - #583
Conversation
c106170 to
0db7106
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds the TBOR RSA‑AES key import command pair (GetUnwrappingKey / UnwrapKey) to allow a host to wrap key material to a partition and re-import it as a masked (unmask-on-use) blob, plus supporting plumbing (shared session/masking helpers, HashAlgo wire enum) and tests/docs. It also updates the SessionEx schedule to use a 32‑byte AES‑GCM masking key (TBOR) while keeping legacy MBOR’s 80‑byte CBC masking key schedule.
Changes:
- Introduces TBOR opcodes
0x13(GetUnwrappingKey) and0x14(UnwrapKey) end-to-end: wire types, dispatcher routing, handlers, host wrappers, and docs. - Adds shared TBOR helper(s) for active-session validation and resolving per-scope masking keys; moves/introduces
HashAlgoin sharedkey_props. - Extends key decode to support HMAC key material (32/48/64 B →
_HmacSha*) and adds TBOR derive support for “reserve + fill later” for mutable buffer fields.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| fw/plat/uno/fw/pal/src/session.rs | Updates SessionEx blob sizing/validation to use 32-byte masking key for TBOR. |
| fw/plat/std/pal/src/session.rs | Updates SessionEx blob sizing and makes session masking-key extraction length-aware (legacy vs SessionEx). |
| fw/pal/traits/src/lib.rs | Changes SESSION_MASKING_KEY_LEN to 32 and clarifies it’s for TBOR SessionEx masking. |
| fw/core/lib/src/op.rs | Classifies new TBOR opcodes as InSession for session-flag validation. |
| fw/core/lib/src/ddi/tbor/unwrap_key.rs | New TBOR UnwrapKey handler: unwrap, decode, scope-mask, and respond (incl. pubkey for RSA/ECC). |
| fw/core/lib/src/ddi/tbor/session_open_finish.rs | Updates SessionEx schedule documentation to 32-byte AES-GCM masking key. |
| fw/core/lib/src/ddi/tbor/mod.rs | Adds opcode constants, dispatch routing, active-session validation helper, and masking-key resolver helper. |
| fw/core/lib/src/ddi/tbor/get_unwrapping_key.rs | New TBOR GetUnwrappingKey handler: derives and returns partition RSA unwrapping public key. |
| fw/core/lib/src/ddi/mod.rs | Adds a shared hmac_hash helper at the command level for both codecs. |
| fw/core/lib/src/ddi/mbor/hmac.rs | Switches to shared crate::ddi::hmac_hash. |
| fw/core/lib/src/ddi/mbor/from_pal.rs | Removes the duplicated MBOR-only hmac_hash mapping helper. |
| fw/core/key-decode/src/lib.rs | Adds HMAC as a decode class and wires it into decode(). |
| fw/core/key-decode/src/hmac.rs | New HMAC decode path (length-based classification). |
| fw/core/ddi/tbor/types/src/unwrap_key.rs | New TBOR wire schema for UnwrapKey. |
| fw/core/ddi/tbor/types/src/lib.rs | Exposes new TBOR type modules (get_unwrapping_key, unwrap_key). |
| fw/core/ddi/tbor/types/src/key_props.rs | Expands key-props to include shared HashAlgo wire enum. |
| fw/core/ddi/tbor/types/src/get_unwrapping_key.rs | New TBOR wire schema for GetUnwrappingKey. |
| fw/core/ddi/tbor/derive/src/schema.rs | Documents semantics of mutable fields and the reserve+fill pattern. |
| fw/core/ddi/tbor/derive/src/codegen_enc.rs | Implements encoder generation for <field>_reserve(len) for mutable buffers. |
| docs/tbor-ddi/README.md | Adds 0x13/0x14 to the TBOR command table. |
| docs/tbor-ddi/commands/unwrap_key.md | New documentation page for UnwrapKey. |
| docs/tbor-ddi/commands/get_unwrapping_key.md | New documentation page for GetUnwrappingKey. |
| ddi/tbor/types/tests/commands/unwrap_key.rs | New emu integration tests for UnwrapKey (AES + HMAC). |
| ddi/tbor/types/tests/commands/mod.rs | Registers new TBOR command tests. |
| ddi/tbor/types/tests/commands/get_unwrapping_key.rs | New emu integration tests for GetUnwrappingKey (length/non-zero/stability). |
| ddi/tbor/types/src/unwrap_key.rs | New host-side TBOR request/response wrappers for UnwrapKey. |
| ddi/tbor/types/src/lib.rs | Re-exports new host-side command wrappers. |
| ddi/tbor/types/src/get_unwrapping_key.rs | New host-side TBOR request/response wrappers for GetUnwrappingKey. |
|
Update: this PR now also introduces encoder "reserve + fill" support in the TBOR derive. For any |
0db7106 to
b3e53f6
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 29 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (3)
fw/core/ddi/tbor/types/src/get_unwrapping_key.rs:9
- The rustdoc link for
UnwrapKeypoints tocrate::hmac, which is unrelated and breaks navigation for readers of the wire schema docs.
//! `GetUnwrappingKey` is an in-session command that returns the
//! partition's RSA-2048 **unwrapping** public key, which the host uses to
//! RSA-AES key-wrap a payload for [`UnwrapKey`](crate::hmac). The
//! unwrapping key is a device-provisioned partition-internal key; only its
fw/core/ddi/tbor/types/src/unwrap_key.rs:59
- The constants are described as being "pinned" into
#[tbor(... max_len = ...)]literals, but there are no compile-time assertions to prevent them from drifting out of sync (a pattern used in other TBOR type schemas).
pub const UNWRAP_PUB_KEY_MAX_LEN: usize = 520;
fw/core/lib/src/ddi/tbor/mod.rs:229
resolve_masking_keyusespal.session_masking_key()forKeyScope::Session, but the Uno PAL currently returnsUnsupportedCmdfromsession_masking_key(fw/plat/uno/fw/pal/src/session.rs:333-335). This makesSession-scoped masking fail on Uno/hardware even though the docs and handler comments indicate it should work for any Active session.
) -> HsmResult<&'p DmaBuf> {
match scope {
HsmKeyScope::Session => pal.session_masking_key(io, sess_id),
_ => {
let mk_key_id = masking_key_id_for_scope(pal, io, scope)?;
pal.vault_key(io, mk_key_id)
}
Add the TBOR Hash command (opcode 0x1B): within an open session, compute a SHA-256 / 384 / 512 digest of a host-supplied message and return it. A pure hashing utility — no key, no scope, no partition state — the TBOR analogue of MBOR ShaDigest. Named generically (Hash rather than ShaDigest) to leave room for future XOF / SHAKE variants. Uses the reserve-then-fill pattern: the response frame is encoded with the digest slot reserved, then the PAL hashes straight into it (natural big-endian output) — no intermediate buffer, no copy. Reuses the shared HashAlgo enum from key_props. Wire schemas (fw + host mirror); dispatch / classifier / op.rs wiring (in-session, session-id cross-checked, default-PSK gated). Emu tests verify device digests byte-for-byte against host-computed SHA (azihsm_crypto) for all three algorithms over short / empty / long messages, plus an unknown-algo reject. Per-command docs added. Based on tbor/unwrap-key (#583) for the shared HashAlgo enum; uses opcode 0x1B to avoid collision with the sibling AES / ECC / RSA crypto branches (0x15..0x1A). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
b3e53f6 to
427588b
Compare
Add the TBOR RsaModExp command (opcode 0x1A): the RSA private-key primitive x = y^d mod n using a caller-held masked RSA private key (imported via UnwrapKey with the RSA / RSA-CRT key class). The device unmasks the key in place (recovering its modulus size and CRT form from the blob's key kind), checks the op's usage attribute (Decrypt -> decrypt, Sign -> sign), computes the modular exponentiation into the reserved response slot, and scrubs the recovered key on every path. The raw primitive underlying RSA decrypt / sign — the host applies and removes any padding. TBOR analogue of MBOR RsaModExp; there is no TBOR RSA key generation, so RSA keys enter only through UnwrapKey. Wire schemas (fw + host mirror) with a 1-byte RsaOp selector; dispatch / classifier / op.rs wiring; a strict from_pal::rsa_key mapping (rejects non-RSA-private kinds as InvalidKeyType). Emu tests cover host-verified RSA sign/decrypt roundtrips for 2K/3K/4K and 4K-CRT through the full UnwrapKey(Rsa) -> RsaModExp path, plus wrong-y-length and wrong-key-class rejects. Per-command docs added. Based on tbor/unwrap-key (#583), which provides the DMA-budget-efficient UnwrapKey that lets RSA-3072/4096 keys be imported within 8 KB. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
Add the TBOR Hash command (opcode 0x1B): within an open session, compute a SHA-256 / 384 / 512 digest of a host-supplied message and return it. A pure hashing utility — no key, no scope, no partition state — the TBOR analogue of MBOR ShaDigest. Named generically (Hash rather than ShaDigest) to leave room for future XOF / SHAKE variants. Uses the reserve-then-fill pattern: the response frame is encoded with the digest slot reserved, then the PAL hashes straight into it (natural big-endian output) — no intermediate buffer, no copy. Reuses the shared HashAlgo enum from key_props. Wire schemas (fw + host mirror); dispatch / classifier / op.rs wiring (in-session, session-id cross-checked, default-PSK gated). Emu tests verify device digests byte-for-byte against host-computed SHA (azihsm_crypto) for all three algorithms over short / empty / long messages, plus an unknown-algo reject. Per-command docs added. Based on tbor/unwrap-key (#583) for the shared HashAlgo enum; uses opcode 0x1B to avoid collision with the sibling AES / ECC / RSA crypto branches (0x15..0x1A). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 29 out of 29 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (3)
fw/core/ddi/tbor/types/src/get_unwrapping_key.rs:9
- Rustdoc link target is incorrect:
crate::hmacdoesn’t exist in this module and will produce a broken intra-doc link. This should point to theunwrap_keyschema/module.
//! `GetUnwrappingKey` is an in-session command that returns the
//! partition's RSA-2048 **unwrapping** public key, which the host uses to
//! RSA-AES key-wrap a payload for [`UnwrapKey`](crate::hmac). The
//! unwrapping key is a device-provisioned partition-internal key; only its
fw/core/ddi/tbor/types/src/unwrap_key.rs:59
- The max-len constants are described as “pinned” to
#[tbor(... max_len = ...)]literals, but there are noconst _: () = assert!(...)tripwires to keep them in sync. Other TBOR schemas use these asserts to prevent silent drift (e.g.get_unwrapping_key.rs).
/// Max recovered public-key length (RSA-4096 `n_le(512) ‖ e_le(4)`).
/// Pinned into the `#[tbor(buffer, max_len = 520)]` literal on
/// [`TborUnwrapKeyResp::pub_key`].
pub const UNWRAP_PUB_KEY_MAX_LEN: usize = 520;
fw/core/lib/src/ddi/tbor/mod.rs:239
resolve_masking_keyassumesHsmKeyScope::Sessioncan always be satisfied viapal.session_masking_key(...), but on the Uno PAL this method currently returnsUnsupportedCmd(seefw/plat/uno/fw/pal/src/session.rs:333-335). This meansSession-scoped masking (documented as supported) will fail on hardware unless the Uno PAL implementssession_masking_keyforSessionExblobs (32-byte AES-GCM key).
/// Resolve the 32-byte AES-256-GCM masking key material for `scope`,
/// returning a borrow of the on-device key.
///
/// `Session` scope resolves to the per-session masking key
/// ([`HsmSessionManager::session_masking_key`](azihsm_fw_hsm_pal_traits::HsmSessionManager::session_masking_key));
/// every other scope resolves via [`masking_key_id_for_scope`] and reads
/// the vault key. All scopes yield a 32-byte key suitable for the
/// `key_masking::aead` (AES-GCM-256) masked-key system.
fn resolve_masking_key<'p, P: HsmPal>(
pal: &'p P,
io: &impl HsmIo,
scope: HsmKeyScope,
sess_id: HsmSessId,
) -> HsmResult<&'p DmaBuf> {
match scope {
HsmKeyScope::Session => pal.session_masking_key(io, sess_id),
_ => {
let mk_key_id = masking_key_id_for_scope(pal, io, scope)?;
pal.vault_key(io, mk_key_id)
}
}
427588b to
db29d52
Compare
Add the TBOR RsaModExp command (opcode 0x1A): the RSA private-key primitive x = y^d mod n using a caller-held masked RSA private key (imported via UnwrapKey with the RSA / RSA-CRT key class). The device unmasks the key in place (recovering its modulus size and CRT form from the blob's key kind), checks the op's usage attribute (Decrypt -> decrypt, Sign -> sign), computes the modular exponentiation into the reserved response slot, and scrubs the recovered key on every path. The raw primitive underlying RSA decrypt / sign — the host applies and removes any padding. TBOR analogue of MBOR RsaModExp; there is no TBOR RSA key generation, so RSA keys enter only through UnwrapKey. Wire schemas (fw + host mirror) with a 1-byte RsaOp selector; dispatch / classifier / op.rs wiring; a strict from_pal::rsa_key mapping (rejects non-RSA-private kinds as InvalidKeyType). Emu tests cover host-verified RSA sign/decrypt roundtrips for 2K/3K/4K and 4K-CRT through the full UnwrapKey(Rsa) -> RsaModExp path, plus wrong-y-length and wrong-key-class rejects. Per-command docs added. Based on tbor/unwrap-key (#583), which provides the DMA-budget-efficient UnwrapKey that lets RSA-3072/4096 keys be imported within 8 KB. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
Add the TBOR Hash command (opcode 0x1B): within an open session, compute a SHA-256 / 384 / 512 digest of a host-supplied message and return it. A pure hashing utility — no key, no scope, no partition state — the TBOR analogue of MBOR ShaDigest. Named generically (Hash rather than ShaDigest) to leave room for future XOF / SHAKE variants. Uses the reserve-then-fill pattern: the response frame is encoded with the digest slot reserved, then the PAL hashes straight into it (natural big-endian output) — no intermediate buffer, no copy. Reuses the shared HashAlgo enum from key_props. Wire schemas (fw + host mirror); dispatch / classifier / op.rs wiring (in-session, session-id cross-checked, default-PSK gated). Emu tests verify device digests byte-for-byte against host-computed SHA (azihsm_crypto) for all three algorithms over short / empty / long messages, plus an unknown-algo reject. Per-command docs added. Based on tbor/unwrap-key (#583) for the shared HashAlgo enum; uses opcode 0x1B to avoid collision with the sibling AES / ECC / RSA crypto branches (0x15..0x1A). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
fw/pal/traits/src/lib.rs:440
- The doc for
SESSION_MASKING_KEY_LENsays the legacy MBOR Session masking key is “sized by the std-PAL-localSESSION_MASKING_KEY_SIZE”, but the same local constant exists in other PALs too (e.g. Uno). This wording is misleading for implementers/readers trying to locate the legacy sizing constant.
/// Present for both CO and CU SessionEx blobs. Distinct from the legacy
/// MBOR `Session`-blob masking key (an 80 B `aes32 ‖ hmac48` cbc key,
/// sized by the std-PAL-local `SESSION_MASKING_KEY_SIZE`), which the
/// `key_masking::cbc`-based MBOR masked-key system continues to use.
…ey len) - Rename the `oaep_hash` request field to `oaep_hash_algo` (fw + host wire types, handler, tests, docs) for clarity, per review. - `resolve_masking_key` now validates that a `Session`-scope masking key is exactly SESSION_MASKING_KEY_LEN (32) bytes, rejecting a legacy 80-byte MBOR `Session` blob key with `UnsupportedKeyScope` so a TBOR masked-key command can't accidentally run under a legacy session schedule. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6cd9bfef-fd58-4ac9-bbb0-eb67febe865d
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 30 out of 30 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
fw/pal/traits/src/lib.rs:436
SESSION_MASKING_KEY_LENis updated to 32 bytes for TBORSessionEx, but theHsmVaultKeyKind::SessionExdocumentation still describes the SessionEx blob as carryingmasking_key(80)with sizes 120/216 (seefw/pal/traits/src/vault.rs:160-164). Please update that enum doc (and any other related docs) to reflect the newmasking_key(32)layout (72/168) so implementers don’t rely on the old sizing.
/// Length in bytes of the per-session `masking_key` for TBOR
/// **SessionEx (CU/CO)** sessions.
///
/// 32 B AES-256-GCM key used by the `key_masking::aead`-based TBOR
/// masked-key system (masked keys are scoped and wrapped under AES-GCM).
Address review feedback (jaygmsft): the handler hardcoded both permissions per key class (e.g. RSA always got sign+decrypt), deviating from the MBOR import path where the caller selects the usage. Add a compact 1-byte `KeyUsage` bitfield to the shared TBOR `key_props` (encrypt/decrypt/sign/verify/derive/wrap/unwrap) and carry it on the `UnwrapKey` request. The handler now derives the recovered key's vault attributes from the requested usage, mirroring the MBOR `key_attrs` policy: sign+verify and encrypt+decrypt are matched pairs, exactly one usage group may be set, and each class restricts the valid group(s) (AES: enc/dec; RSA: sign/verify or enc/dec; ECC: sign/verify or derive; HMAC: sign/verify). Invalid combinations are rejected with InvalidPermissions. Adds host KEY_USAGE_* constants, an emu rejection test, and doc updates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6cd9bfef-fd58-4ac9-bbb0-eb67febe865d
Address review feedback: the SessionEx masking key is now 32 B (SESSION_MASKING_KEY_LEN, AES-256-GCM), so the SessionEx blob is 72 B (PlainText/CU) / 168 B (Authenticated/CO), not the old 120/216 B with an 80 B masking key. Update the stale `HsmVaultKeyKind::SessionEx` and `session_promote` doc comments accordingly; the legacy MBOR `Session` blob's 80 B CBC key is called out as distinct. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6cd9bfef-fd58-4ac9-bbb0-eb67febe865d
…inal; add ECC unwrap test Address review feedback: - `masking_key_id_for_scope` now remaps `PartPropNotFound` to `UnsupportedKeyScope` for `Ephemeral`/`Local` scopes too (not just `SecurityDomain`), so requesting a scope before its masking key is provisioned surfaces the single documented `UnsupportedKeyScope` instead of leaking `PartPropNotFound`. Docs corrected accordingly (the prior `InvalidArg`-before-`Initialized` claim was inaccurate). - Add an emu `UnwrapKey` round-trip for the ECC (P-256) key class, exercising the PKCS#8 decode + re-derived `x‖y` public-key path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6cd9bfef-fd58-4ac9-bbb0-eb67febe865d
Jayant Gandhi (jaygmsft)
left a comment
There was a problem hiding this comment.
Approving with suggestions
Add the TBOR RsaModExp command (opcode 0x1A): the RSA private-key primitive x = y^d mod n using a caller-held masked RSA private key (imported via UnwrapKey with the RSA / RSA-CRT key class). The device unmasks the key in place (recovering its modulus size and CRT form from the blob's key kind), checks the op's usage attribute (Decrypt -> decrypt, Sign -> sign), computes the modular exponentiation into the reserved response slot, and scrubs the recovered key on every path. The raw primitive underlying RSA decrypt / sign — the host applies and removes any padding. TBOR analogue of MBOR RsaModExp; there is no TBOR RSA key generation, so RSA keys enter only through UnwrapKey. Wire schemas (fw + host mirror) with a 1-byte RsaOp selector; dispatch / classifier / op.rs wiring; a strict from_pal::rsa_key mapping (rejects non-RSA-private kinds as InvalidKeyType). Emu tests cover host-verified RSA sign/decrypt roundtrips for 2K/3K/4K and 4K-CRT through the full UnwrapKey(Rsa) -> RsaModExp path, plus wrong-y-length and wrong-key-class rejects. Per-command docs added. Based on tbor/unwrap-key (#583), which provides the DMA-budget-efficient UnwrapKey that lets RSA-3072/4096 keys be imported within 8 KB. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
Add the TBOR Hash command (opcode 0x1B): within an open session, compute a SHA-256 / 384 / 512 digest of a host-supplied message and return it. A pure hashing utility — no key, no scope, no partition state — the TBOR analogue of MBOR ShaDigest. Named generically (Hash rather than ShaDigest) to leave room for future XOF / SHAKE variants. Uses the reserve-then-fill pattern: the response frame is encoded with the digest slot reserved, then the PAL hashes straight into it (natural big-endian output) — no intermediate buffer, no copy. Reuses the shared HashAlgo enum from key_props. Wire schemas (fw + host mirror); dispatch / classifier / op.rs wiring (in-session, session-id cross-checked, default-PSK gated). Emu tests verify device digests byte-for-byte against host-computed SHA (azihsm_crypto) for all three algorithms over short / empty / long messages, plus an unknown-algo reject. Per-command docs added. Based on tbor/unwrap-key (#583) for the shared HashAlgo enum; uses opcode 0x1B to avoid collision with the sibling AES / ECC / RSA crypto branches (0x15..0x1A). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
Add the TBOR RsaModExp command (opcode 0x1A): the RSA private-key primitive x = y^d mod n using a caller-held masked RSA private key (imported via UnwrapKey with the RSA / RSA-CRT key class). The device unmasks the key in place (recovering its modulus size and CRT form from the blob's key kind), checks the op's usage attribute (Decrypt -> decrypt, Sign -> sign), computes the modular exponentiation into the reserved response slot, and scrubs the recovered key on every path. The raw primitive underlying RSA decrypt / sign — the host applies and removes any padding. TBOR analogue of MBOR RsaModExp; there is no TBOR RSA key generation, so RSA keys enter only through UnwrapKey. Wire schemas (fw + host mirror) with a 1-byte RsaOp selector; dispatch / classifier / op.rs wiring; a strict from_pal::rsa_key mapping (rejects non-RSA-private kinds as InvalidKeyType). Emu tests cover host-verified RSA sign/decrypt roundtrips for 2K/3K/4K and 4K-CRT through the full UnwrapKey(Rsa) -> RsaModExp path, plus wrong-y-length and wrong-key-class rejects. Per-command docs added. Based on tbor/unwrap-key (Azure#583), which provides the DMA-budget-efficient UnwrapKey that lets RSA-3072/4096 keys be imported within 8 KB. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
Add the TBOR Hash command (opcode 0x1B): within an open session, compute a SHA-256 / 384 / 512 digest of a host-supplied message and return it. A pure hashing utility — no key, no scope, no partition state — the TBOR analogue of MBOR ShaDigest. Named generically (Hash rather than ShaDigest) to leave room for future XOF / SHAKE variants. Uses the reserve-then-fill pattern: the response frame is encoded with the digest slot reserved, then the PAL hashes straight into it (natural big-endian output) — no intermediate buffer, no copy. Reuses the shared HashAlgo enum from key_props. Wire schemas (fw + host mirror); dispatch / classifier / op.rs wiring (in-session, session-id cross-checked, default-PSK gated). Emu tests verify device digests byte-for-byte against host-computed SHA (azihsm_crypto) for all three algorithms over short / empty / long messages, plus an unknown-algo reject. Per-command docs added. Based on tbor/unwrap-key (#583) for the shared HashAlgo enum; uses opcode 0x1B to avoid collision with the sibling AES / ECC / RSA crypto branches (0x15..0x1A). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
Add the TBOR Hash command (opcode 0x1B): within an open session, compute a SHA-256 / 384 / 512 digest of a host-supplied message and return it. A pure hashing utility — no key, no scope, no partition state — the TBOR analogue of MBOR ShaDigest. Named generically (Hash rather than ShaDigest) to leave room for future XOF / SHAKE variants. Uses the reserve-then-fill pattern: the response frame is encoded with the digest slot reserved, then the PAL hashes straight into it (natural big-endian output) — no intermediate buffer, no copy. Reuses the shared HashAlgo enum from key_props. Wire schemas (fw + host mirror); dispatch / classifier / op.rs wiring (in-session, session-id cross-checked, default-PSK gated). Emu tests verify device digests byte-for-byte against host-computed SHA (azihsm_crypto) for all three algorithms over short / empty / long messages, plus an unknown-algo reject. Per-command docs added. Based on tbor/unwrap-key (#583) for the shared HashAlgo enum; uses opcode 0x1B to avoid collision with the sibling AES / ECC / RSA crypto branches (0x15..0x1A). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
Add the TBOR Hash command (opcode 0x1B): within an open session, compute a SHA-256 / 384 / 512 digest of a host-supplied message and return it. A pure hashing utility — no key, no scope, no partition state — the TBOR analogue of MBOR ShaDigest. Named generically (Hash rather than ShaDigest) to leave room for future XOF / SHAKE variants. Uses the reserve-then-fill pattern: the response frame is encoded with the digest slot reserved, then the PAL hashes straight into it (natural big-endian output) — no intermediate buffer, no copy. Reuses the shared HashAlgo enum from key_props. Wire schemas (fw + host mirror); dispatch / classifier / op.rs wiring (in-session, session-id cross-checked, default-PSK gated). Emu tests verify device digests byte-for-byte against host-computed SHA (azihsm_crypto) for all three algorithms over short / empty / long messages, plus an unknown-algo reject. Per-command docs added. Based on tbor/unwrap-key (Azure#583) for the shared HashAlgo enum; uses opcode 0x1B to avoid collision with the sibling AES / ECC / RSA crypto branches (0x15..0x1A). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0b09e50a-a9be-4bae-b347-d42dc775a258
Summary
Implements the TBOR key-import pair that lets a host RSA-AES-wrap a
key to a partition and re-import it as a masked (unmask-on-use) blob
— the TBOR analogue of MBOR
RsaUnwrap, but re-masking the recoveredkey under a scope's masking key instead of vaulting it under a key
handle.
GetUnwrappingKey(opcode0x13)Returns the partition's RSA-2048 unwrapping public key in HSM wire
format (
n_le(256) ‖ e_le(4)). The private half never leaves thedevice;
UnwrapKeyresolves it internally by the partition'sRSA_UNWRAPPING_KEY_IDproperty. Materialised lazily (std/emulator PAL)or generated in the background from partition init (hardware); an absent
key surfaces as
PendingKeyGenerationso the host retries. Both CO/CU.UnwrapKey(opcode0x14)Within an open CO/CU session, unwrap a host-supplied
RSA-OAEP(KEK) ‖ AES-KWP(key)blob with the partition unwrapping key andreturn the recovered key masked (AEAD-GCM-256) under the requested
scope's masking key. Supports the AES, RSA (plain/CRT), ECC, and HMAC
key classes via the shared
azihsm_fw_hsm_key_unwrap/azihsm_fw_hsm_key_decodecrates; the decode crate gains an HMAC path(host-selected SHA variant → the variable-length
VarLenHmacShaNkinds). The re-derived wire public key is returnedfor the asymmetric (RSA/ECC) classes; symmetric classes return an empty
pub_key. Persists nothing. Both CO/CU.Shared helpers introduced here
validate_active_sessionandresolve_masking_key(scope, sess_id)(Session → per-session key; Ephemeral/Local/SecurityDomain → scope
vault key).
HashAlgowire enum, moved into the sharedkey_propsmodule (itselects the OAEP hash here and, in feat(tbor): implement HmacGenerateKey + Hmac (masked-key HMAC crypto) #580, the HMAC SHA variant).
Tests / validation
GetUnwrappingKey;UnwrapKeyAES (masked, nopubkey) and HMAC key-class (recovered kind =
VarLenHmacSha256). The fullunwrap→MAC round-trip lives in feat(tbor): implement HmacGenerateKey + Hmac (masked-key HMAC crypto) #580, which builds on this command.
clippyclean;nightly
fmt+copyrightclean; Uno (thumbv7em) build passes.docs/tbor-ddi/commands/{get_unwrapping_key,unwrap_key}.md+README rows (
0x13,0x14).Endianness note (host wrap)
GetUnwrappingKeyreturns the modulus/exponent little-endian, and thedevice expects the OAEP ciphertext in wire-LE (it flips to big-endian
internally for OpenSSL). The emu test's host-side wrap reverses the
pubkey components (LE→BE for
from_hsm_bytes) and the RSA-OAEPciphertext (BE→LE) accordingly.